/*=============================================================================
Copyright (c) 2012 Crytek Studios. All Rights Reserved.

Revision history:
	* Created by Nicolas Schulz, February 2012

=============================================================================*/

#include "StdAfx.h"
#include "PipelineProfiler.h"
#include "DriverD3D.h"


CRenderPipelineProfiler::CRenderPipelineProfiler()
{
	m_numSections = 0;
	m_gpuSyncTime = 0;
	m_enabled = false;
	m_recordData = false;
	m_waitForGPUTimers = false;

	m_stack.reserve( 8 );

	ResetBasicStats();
}


void CRenderPipelineProfiler::BeginFrame()
{
	m_recordData = IsEnabled();
	
	if( m_numSections > 0 )
	{
		m_sections[m_numSections - 1].gpuTimer.UpdateTime();
		if( m_sections[m_numSections - 1].gpuTimer.HasPendingQueries() )
		{
			// Don't record new data if there are still pending GPU time queries, otherwise results may be wrong when labels change between frames
			m_recordData = false;
		}
		else
		{
			UpdateBasicStats();
			m_numSections = 0;
		}
	}

	BeginSection( "FRAME", 0 );
	if( m_recordData )
	{
		m_sections[0].numDIPs = 0;
		m_sections[0].numPolys = 0;
	}
}


void CRenderPipelineProfiler::EndFrame()
{
	EndSection( "FRAME" );

	if( !m_stack.empty() )
	{
		m_sections[0].recLevel = -1;
		m_stack.resize( 0 );
	}
	
	m_gpuSyncTime = 0;

	if( m_numSections > 0 && (m_waitForGPUTimers || CRenderer::CV_r_stats == 16) )
	{
		CTimeValue startTime = gEnv->pTimer->GetAsyncTime();
		
		// This will lower the overall framerate but gives accurate GPU times
		do
		{
			 m_sections[m_numSections - 1].gpuTimer.UpdateTime();
		} while( m_sections[m_numSections - 1].gpuTimer.HasPendingQueries() );

		m_gpuSyncTime = gEnv->pTimer->GetAsyncTime().GetDifferenceInSeconds( startTime ) * 1000.0f;

		UpdateBasicStats();
	}

	m_recordData = false;

	if( CRenderer::CV_r_stats == 16 )
		DisplayAdvancedStats();
	else if( CRenderer::CV_r_stats == 17 )
		DisplayBasicStats();
}


bool CRenderPipelineProfiler::FilterLabel( const char *name )
{
	return
		(strcmp( name, "SCREEN_STRETCH_RECT" ) == 0) ||
		(strcmp( name, "STRETCHRECT_EMU" ) == 0) ||
		(strcmp( name, "STENCIL_VOLUME" ) == 0) ||
		(strcmp( name, "DRAWSTRINGW" ) == 0);  // FIXME: Label stack error in DRAWSTRINGW
}


void CRenderPipelineProfiler::BeginSection( const char *name, uint32 eProfileLabelFlags )
{
	if( m_numSections >= MaxNumSections )
		m_recordData = false;
	
	if( !m_recordData || FilterLabel(name) )
		return;
	
	RPProfilerSection &section = m_sections[m_numSections++];

	strncpy( section.name, name, 30 );
	section.name[30] = '\0';

	section.recLevel = m_stack.size() + 1;
	section.numDIPs = gcpRendD3D->GetCurrentNumberOfDrawCalls();
	section.numPolys = gcpRendD3D->RT_GetPolyCount();
	section.startTimeCPU = gEnv->pTimer->GetAsyncTime();
	section.gpuTimer.Start();
	
	m_stack.push_back( m_numSections - 1 );
}


void CRenderPipelineProfiler::EndSection( const char *name )
{
	if( !m_recordData || FilterLabel(name) )
		return;
	
	if( !m_stack.empty() )
	{
		RPProfilerSection &section = m_sections[m_stack.back()];
		
		section.numDIPs = gcpRendD3D->GetCurrentNumberOfDrawCalls() - section.numDIPs;
		section.numPolys = gcpRendD3D->RT_GetPolyCount() - section.numPolys;
		section.endTimeCPU = gEnv->pTimer->GetAsyncTime();
		section.gpuTimer.Stop();
		if( strncmp( section.name, name, 30 ) != 0 )
			section.recLevel = -section.recLevel;

		m_stack.pop_back();
	}
}


ILINE void AddToStats( RPProfilerStats &outStats, RPProfilerSection &section )
{
	outStats.gpuTime += section.gpuTimer.GetTime();
	outStats.cpuTime += section.endTimeCPU.GetDifferenceInSeconds( section.startTimeCPU ) * 1000.0f;
	outStats.numDIPs += section.numDIPs;
	outStats.numPolys += section.numPolys;
}


void CRenderPipelineProfiler::ResetBasicStats()
{
	for( uint32 i = 0; i < RPPSTATS_NUM; ++i )
	{
		m_basicStats[i].gpuTime = m_basicStats[i].cpuTime = 0.0f;
		m_basicStats[i].numDIPs = m_basicStats[i].numPolys = 0;
	}
}


void CRenderPipelineProfiler::UpdateBasicStats()
{
	ResetBasicStats();
	
	for( uint32 i = 0; i < m_numSections; ++i )
	{
		m_sections[i].gpuTimer.UpdateTime();

		if( strcmp( m_sections[i].name, "SCENE_REC" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_SceneRecursion], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "SHADOWMAP PASSES" ) == 0 ||
		         strcmp( m_sections[i].name, "SHADOWMASK" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_SceneShadows], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "ZPASS" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_SceneZPass], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "OPAQUE_PASSES" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_SceneOpaque], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "TRANSPARENT_PASSES" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_SceneTransparent], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "DEFERRED_LIGHTING" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_DeferredProcessing], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "DEFERRED_CUBEMAPS" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_DeferredCubemaps], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "DEFERRED_LIGHTS" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_DeferredLights], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "HDR_POSTPROCESS" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_HDRPostProcessing], m_sections[i] );
		}
		else if( strcmp( m_sections[i].name, "POST EFFECTS" ) == 0 )
		{
			AddToStats( m_basicStats[eRPPSTATS_PostProcessing], m_sections[i] );
		}
	}

	AddToStats( m_basicStats[eRPPSTATS_OverallFrame], m_sections[0] );
}


void CRenderPipelineProfiler::DisplayAdvancedStats()
{
#ifndef _RELEASE	
	CD3D9Renderer *rd = gcpRendD3D;
	
	uint32 elemsPerColumn = (gcpRendD3D->GetHeight() - 60) / 16;
	
	ColorF color = ColorF( 1.0f, 1.0f, 0.2f, 1 );
	rd->Draw2dLabel( 20, 10, 1.7f, &color.r, false, "FPS %.1f  GPU Sync %.1fms", 1.0f / gEnv->pTimer->GetFrameTime(), m_gpuSyncTime );
	color.r = color.g = color.b = 0.35f;
	rd->Draw2dLabel( 320, 10, 1.5f, &color.r, false, "GPU" );
	rd->Draw2dLabel( 400, 10, 1.5f, &color.r, false, "CPU" );
	rd->Draw2dLabel( 470, 10, 1.5f, &color.r, false, "DIPs" );
	rd->Draw2dLabel( 520, 10, 1.5f, &color.r, false, "Polys" );
	
	if( m_numSections > elemsPerColumn )
	{
		rd->Draw2dLabel( 320 + 600, 10, 1.5f, &color.r, false, "GPU" );
		rd->Draw2dLabel( 400 + 600, 10, 1.5f, &color.r, false, "CPU" );
		rd->Draw2dLabel( 470 + 600, 10, 1.5f, &color.r, false, "DIPs" );
		rd->Draw2dLabel( 520 + 600, 10, 1.5f, &color.r, false, "Polys" );
	}
	
	for( uint32 column = 0; column < 2; ++column )
	{
		uint32 firstElem = column * elemsPerColumn;
		uint32 lastElem = min( (column + 1) * elemsPerColumn, m_numSections );
		float xpos = 20.0f + column * 600.0f;

		assert(lastElem < MaxNumSections);
		for( uint32 i = firstElem; i < lastElem ; ++i )
		{
			RPProfilerSection &section = m_sections[i];
			
			float gpuTime = section.gpuTimer.GetTime();
			float ypos = 30.0f + (i % elemsPerColumn) * 16.0f;
			color.r = color.g = color.b = min( 0.4f + gpuTime * 0.4f, 0.9f );

			if( section.recLevel < 0 )  // Label stack error
			{
				color.r = 1; color.g = color.b = 0;
			}
			else if( i == 0 )  // Special case for FRAME section
			{
				color.r = 1; color.g = 1; color.b = 0.2f;
			}
		
			rd->Draw2dLabel( xpos + max((abs(section.recLevel) - 2), 0) * 15.0f, ypos, 1.5f, &color.r, false, section.name );
			rd->Draw2dLabel( xpos + 300, ypos, 1.5f, &color.r, false, "%.2fms", gpuTime );
			rd->Draw2dLabel( xpos + 380, ypos, 1.5f, &color.r, false, "%.2fms", section.endTimeCPU.GetDifferenceInSeconds( section.startTimeCPU ) * 1000.0f );
			rd->Draw2dLabel( xpos + 450, ypos, 1.5f, &color.r, false, "%i", section.numDIPs );
			rd->Draw2dLabel( xpos + 500, ypos, 1.5f, &color.r, false, "%i", section.numPolys );
		}
	}

	rd->RT_FlushTextMessages();
#endif
}


void CRenderPipelineProfiler::DisplayBasicStats()
{
	CD3D9Renderer *rd = gcpRendD3D;

	struct StatsGroup
	{
		char                          name[32];
		ERenderPipelineProfilerStats  statIndex;
	};
	
	StatsGroup statsGroups[] = {
		{ "Scene", eRPPSTATS_OverallFrame },
		{ "  Reflections", eRPPSTATS_SceneRecursion },
		{ "  Shadows", eRPPSTATS_SceneShadows },
		{ "  ZPass", eRPPSTATS_SceneZPass },
		{ "  Deferred Processing", eRPPSTATS_DeferredProcessing },
		{ "    Cubemaps", eRPPSTATS_DeferredCubemaps },
		{ "    Lights", eRPPSTATS_DeferredLights },
		{ "  Opaque", eRPPSTATS_SceneOpaque },
		{ "  Transparent", eRPPSTATS_SceneTransparent },
		{ "  HDR Processing", eRPPSTATS_HDRPostProcessing },
		{ "  PostFX", eRPPSTATS_PostProcessing }
	};

	ColorF color = Col_SteelBlue;
	float xpos = 40, ypos = 120;
	
	color.r = color.g = color.b = 0.35f;
	rd->Draw2dLabel( xpos + 300, ypos - 20, 1.5f, &color.r, false, "GPU" );
	rd->Draw2dLabel( xpos + 400, ypos - 20, 1.5f, &color.r, false, "CPU" );
	rd->Draw2dLabel( xpos + 500, ypos - 20, 1.5f, &color.r, false, "Drawcalls" );
	rd->Draw2dLabel( xpos + 600, ypos - 20, 1.5f, &color.r, false, "Polys" );

	color = Col_SteelBlue;
	
	for( uint32 i = 0, si = sizeof( statsGroups ) / sizeof( StatsGroup ); i < si; ++i )
	{
		const RPProfilerStats &stats = m_basicStats[statsGroups[i].statIndex];
		
		rd->Draw2dLabel( xpos, ypos + i * 20.0f, 2.0f, &color.r, false, "%s", statsGroups[i].name );
		rd->Draw2dLabel( xpos + 300, ypos + i * 20.0f, 2.0f, &color.r, false, "%.2fms", stats.gpuTime );
		rd->Draw2dLabel( xpos + 400, ypos + i * 20.0f, 2.0f, &color.r, false, "%.2fms", stats.cpuTime );
		rd->Draw2dLabel( xpos + 500, ypos + i * 20.0f, 2.0f, &color.r, false, "%i", stats.numDIPs );
		rd->Draw2dLabel( xpos + 600, ypos + i * 20.0f, 2.0f, &color.r, false, "%.1f K", stats.numPolys / 1000.0f );
	}

	rd->RT_FlushTextMessages();
}
